﻿#region Using Statements

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

#endregion


namespace Freeform
{

    #region Animation

    /// <summary>
    /// The animation class is used to split a spritesheet up into an animation.
    /// </summary>
    public class Animation
    {

        #region Events

        /// <summary>
        /// This event is fired when an animation has finished playing and the animation has entered a freezeframe state.
        /// </summary>
        public event AnimationFinishedEventHandler AnimationFinished;
        public delegate void AnimationFinishedEventHandler(Animation animation);

        #endregion


        #region Members

        /// <summary>
        /// Gets the image data for the sprite or animation.
        /// </summary>
        public virtual Texture2D Texture { get; set; }


        /// <summary>
        /// Gets the width per animation cell.
        /// </summary>
        public virtual Int32 WidthPerCell { get { return Texture.Width / Columns; } }


        /// <summary>
        /// Gets the height per animation cell.
        /// </summary>
        public virtual Int32 HeightPerCell { get { return Texture.Height / Rows; } }


        /// <summary>
        /// Gets or sets the number of rows in the animation.
        /// </summary>
        public virtual Int32 Rows { get; set; }


        /// <summary>
        /// Gets the number of columns in the animation.
        /// </summary>
        public virtual Int32 Columns { get; set; }


        /// <summary>
        /// Gets the number of frames in the animation.
        /// </summary>
        public virtual Int32 Frames { get { return Rows * Columns; } }


        /// <summary>
        /// Gets or sets the number of frames per second to play for an animation.
        /// </summary>
        public virtual Single FrameRate { get; set; }


        /// <summary>
        /// The number of times that the animation should be played. 1 --> Play Once, 2 --> Twice etc..
        /// 0 --> Loop forever.
        /// </summary>
        public virtual Int32 Loop { get; set; }


        /// <summary>
        /// Gets the number of times the animation has played.
        /// </summary>
        private Int32 PlayCount { get; set; }


        /// <summary>
        /// Gets or sets whether the animation should be frozen.
        /// </summary>
        public virtual Boolean FreezeFrame { get; set; }


        /// <summary>
        /// Gets the name of this animation.
        /// </summary>
        public virtual String AnimationName { get; private set; }


        /// <summary>
        /// The milliseconds until the nextframe in the animation should be played.
        /// </summary>
        protected virtual Int32 NextFrame { get; set; }


        /// <summary>
        /// The index of the current frame being played.
        /// </summary>
        protected virtual Int32 CurrentFrame { get; set; }

        #endregion


        #region Init

        /// <summary>
        /// Creates a new Animation.
        /// </summary>
        /// <param name="game">The game that this animation belongs to.</param>
        /// <param name="textureName">The asset name to load.</param>
        /// <param name="animationName">The name of this animation. (This string is passed to the AnimationSet to tell the animation to play.)</param>
        /// <param name="rows">The number of rows in the sprite-sheet.</param>
        /// <param name="cols">The number of columns in the sprite-sheet.</param>
        /// <param name="framerate">The framerate of the animation.</param>
        public Animation(String textureName, String animationName, int rows, int cols, float framerate)
        {
            Texture = Game.GameRef.Content.Load<Texture2D>(textureName);
            Rows = rows;
            Columns = cols;
            FrameRate = framerate;
            AnimationName = animationName;
            NextFrame = (int)(1000.0f / FrameRate);
        }

        #endregion


        #region Utility

        /// <summary>
        /// Plays the animation.
        /// </summary>
        public virtual void Play()
        {
            PlayCount = 0;
            CurrentFrame = 0;
            FreezeFrame = false;
            NextFrame = (int)(1000.0f / FrameRate);
        }

        #endregion


        #region Update

        /// <summary>
        /// Returns the required source rectangle to access the current frame in the spritesheet.
        /// </summary>
        /// <param name="time">Provides a snapshot of timing values.</param>
        /// <returns>The source rectangle to be used in conjunction with the Texture to draw the correct frame.</returns>
        public virtual Rectangle UpdateSource(GameTime time)
        {
            // Update time till next frame
            if (!FreezeFrame)
            {
                NextFrame -= time.ElapsedGameTime.Milliseconds;
                if (NextFrame < 0)
                {
                    CurrentFrame++;
                    NextFrame += (int)(1000.0f / FrameRate);

                    if (CurrentFrame >= Frames)
                    {
                        PlayCount++;
                        if (PlayCount < Loop || Loop == 0)
                            CurrentFrame = 0;
                        else
                        {
                            CurrentFrame = Frames - 1;
                            FreezeFrame = true;
                            if (AnimationFinished != null)
                                AnimationFinished(this);
                        }
                    }
                }
            }

            // Generate source rectangle
            int width = Texture.Width / Columns;
            int height = Texture.Height / Rows;
            return new Rectangle(width * (CurrentFrame % Columns), height * (CurrentFrame / Columns), width, height);
        }

        #endregion

    }

    #endregion


    #region AnimationSet

    /// <summary>
    /// The animation set allows an entity to have a variety of different animations and controls the playing of these.
    /// </summary>
    public class AnimationSet
    {

        #region Events

        /// <summary>
        /// This event is fired whenever an animation has finished playing.
        /// </summary>
        public event AnimationFinishedEventHandler AnimationFinished;
        public delegate void AnimationFinishedEventHandler(AnimationSet set, Animation animation);

        /// <summary>
        /// Fires the Animation Finished event.
        /// </summary>
        /// <param name="animation">The animation that has finished.</param>
        private void OnAnimationFinished(Animation animation)
        {
            animation.AnimationFinished -= OnAnimationFinished;
            if (AnimationFinished != null)
                AnimationFinished(this, animation);
        }

        #endregion


        #region Members

        /// <summary>
        /// A set of animations that belong to this entity.
        /// </summary>
        protected virtual Dictionary<String, Animation> Animations { get; set; }

        /// <summary>
        /// Gets the currently playing animation.
        /// </summary>
        public virtual Animation Current { get; private set; }

        #endregion


        #region Init

        /// <summary>
        /// Creates a new empty animation set.
        /// </summary>
        public AnimationSet() { }


        /// <summary>
        /// Creates a new animation set.
        /// </summary>
        /// <param name="animation">An animation to add to the set.</param>
        public AnimationSet(Animation animation)
        {
            AddAnimation(animation);
        }

        #endregion


        #region Utility

        /// <summary>
        /// Adds an animation to the animation set.
        /// </summary>
        /// <param name="animation">The animation to be added.</param>
        public virtual void AddAnimation(Animation animation)
        {
            /*  If the current animation is null then the animation set is empty.
             *  In this case we simply add the animation to current without incurring
             *  the memory overhead of instantiating a full animation dictionary.
             *  This is beneficial because many entities only have one animation. */
            if (Current == null)
                Current = animation;

            /*  If the current animation wasn't null but the dictionary is null,
             *  we need to create a new dictionary because we are adding a second
             *  animation. We add the old and the new animation to the dictionary. */
            else if (Animations == null)
            {
                Animations = new Dictionary<string, Animation>();
                Animations.Add(Current.AnimationName, Current);
                Animations.Add(animation.AnimationName, animation);
            }

            /*  Otherwise we are simply adding another animation to the dictionary. */
            else
                Animations.Add(animation.AnimationName, animation);
        }


        /// <summary>
        /// Removes an animation from the animation set.
        /// </summary>
        /// <param name="animationName"></param>
        public virtual void RemoveAnimation(String animationName)
        {
            // Stop current animation from playing if it is the animation to be removed.
            if (Current.AnimationName.CompareTo(animationName) == 0)
                Current = null;

            // If the dictionary exists, remove the animation from there to.
            if (Animations != null)
                Animations.Remove(animationName);
        }


        /// <summary>
        /// Starts playing an animation.
        /// </summary>
        /// <param name="animationName"></param>
        public virtual void PlayAnimation(String animationName)
        {
            if (Current.AnimationName.CompareTo(animationName) == 0) { }
            else if (Animations != null)
            {
                Animation animation;
                if (Animations.TryGetValue(animationName, out animation))
                    Current = animation;
                else
                    throw new Exception("Could not locate animation: " + animationName);
            }
            else return;
            Current.Play();
            Current.AnimationFinished += OnAnimationFinished;
        }


        /// <summary>
        /// Gets the animation of the specified name.
        /// </summary>
        /// <param name="animationName">The name of the animation to find.</param>
        /// <returns>The animation or null if the animation does not exist.</returns>
        public virtual Animation GetAnimation(String animationName)
        {
            // Stop current animation from playing if it is the animation to be removed.
            if (Current.AnimationName.CompareTo(animationName) == 0)
                return Current;

            // If the dictionary exists, remove the animation from there to.
            Animation Result = null;
            if (Animations != null)
                Animations.TryGetValue(animationName, out Result);

            return Result;
        }

        #endregion

    }

    #endregion

}
